<template>
  <a-form ref="formRef"
          :model="formModel"
          v-bind="$attrs"
          class="form"
          :labelCol="labelCol">
    <!-- 本来 v-for 和 v-if 都写在 form-item 中，但优先级 v-if > v-for 有 bug，所以用 div 容器包裹 -->
    <a-row :gutter="24">
      <a-col :span="formItem?.col || 24"
             v-for="formItem in formItems">
        <a-form-item v-if="formItem && formItem._isShow"
                     :key="formItem.key"
                     :label="formItem.label"
                     :rules="formItem._rule"
                     :name="formItem?.key">
          <component :is="formItem.type"
                     v-model:value="formModel[formItem.key]"
                     :options="formItem.options"
                     v-bind="formItem.attrs"
                     :disabled="formItem.disabled"
                     :placeholder="
              formItem.type == 'a-range-picker'
                ? formItem.attrs?.placeholder
                : '请填写' + formItem.label
            ">
          </component>
        </a-form-item>
      </a-col>
    </a-row>
    <!-- 按钮区 -->
    <a-form-item v-if="hasControl">
      <div class="btns">
        <a-button type="primary"
                  @click.prevent="onSubmit">提交校验</a-button>
        <a-button style="margin-left: 10px"
                  @click="OnReset">重置</a-button>
      </div>
    </a-form-item>
  </a-form>
</template>

<!-- 
    FormComponent 功能介绍：
        - 根据传入的配置项动态渲染表单。                                                                                                                                                     
            - 实现表单校验功能，包括必填校验和自定义校验规则。
            - 支持表单联动，通过判断配置项中的函数来控制表单项的显示状态。
            - 可以设置表单的禁用状态。
        - select 下拉框调用接口功能待补充。
        - 维护了原组件的方法和属性，确保不丢失原有功能。
        - 在组件首次挂载时初始化表单数据为默认值。
        - 日期等控件的功能。
        - 栅格布局功能>>>待补充bug。
    解决问题： 
        - 关于实现首次挂载表单数据使用 initFormData 这个方法，注意：在 Vue2 中 data 是可以调用 methods 里面的方法的，但在 Vue3 组合式 API 中需特殊处理。
        - 关于不丢失原组件属性和方法解决办法：
            - ref: 通过 entries 遍历 a-form 的方法，在选项式 mounted 钩子中绑定到 this 上，以便在组件内其他地方可以方便地访问原组件的方法。
            - attrs: 原生属性通过 v-bind:$attrs 传递给子组件，确保子组件能正确接收和使用原组件的属性。
-->
<script>
const inputMap = ['a-input', 'a-input-number', 'a-editor', 'a-textarea']
import { reactive, toRefs } from 'vue'
import { message } from 'ant-design-vue'

export default {
  props: {
    /* 
        控制按钮组(reset 和 submit)。
        类型为布尔类型，默认值为 false。
    */
    hasControl: {
      type: Boolean,
      default: false
    },
    formConfig: {
      type: Array,
      required: true
    },
    labelCol: {
      type: Object,
      default: { style: { width: '80px' } }
    }
  },
  setup(props, { emit }) {
    /* 
        功能：首次挂载组件时初始化表单默认值。
        描述：遍历传入的表单配置项，根据每个配置项的 key 和 defaultValue 属性创建一个初始的表单数据对象 formData。
    */
    const initFormData = () => {
      const formData = {}
      props.formConfig.forEach(item => {
        formData[item.key] = item.defaultValue
      })
      return formData
    }

    const state = reactive({
      formRef: null
    })

    const formModel = reactive(initFormData())

    /* 
        功能：进行表单校验并处理结果。
        描述：调用 formRef 的 validate 方法进行表单校验，若成功则弹出成功提示，否则打印错误信息。
    */
    const onSubmit = () => {
      state.formRef
        .validate()
        .then(() => {
          message.success('校验成功!!!')
        })
        .catch(err => {
          console.log('error', err)
          message.error('error')
        })
    }
    const OnReset = () => {
      console.log()
      /* 
          功能：重置表单字段。
          描述：调用 formRef 的 resetFields 方法重置表单字段。
      */
      state.formRef.resetFields()
    }
    /* 
        功能：计算校验规则。
        描述：根据表单配置项中的规则信息生成校验规则对象，有两种情况：
            - 配置 required(boolean)：必填校验，根据表单项的类型判断触发校验的时机（输入框为 blur，其他可能为 change）。
            - 配置 rule(func)：自定义校验规则待完善，根据不同的表单项类型设置不同的触发校验时机，并支持自定义校验函数。
    */
    const getRule = item => {
      const { validator, required, type, label } = item
      const isInput = inputMap.indexOf(type) > -1
      /* 配置 required: 必填 */
      if (typeof required === 'boolean' && required) {
        return {
          required: true,
          message: `请${isInput ? '输入' : '选择'}${label}`,
          trigger: `${isInput ? 'blur' : 'change'}`
        }
      } else if (isFunction(validator)) {
        /* 配置 rule: 自定义校验规则待完善 */
        return {
          required: true,
          validator: item.validator,
          trigger: `${isInput ? 'blur' : 'change'}`
        }
      } else {
        return null
      }
    }

    /* 
        功能：判断参数是否为 function 类型。
        描述：接收一个参数，判断其是否为函数类型，返回布尔值。
    */
    const isFunction = item => typeof item === 'function'

    /* 
        功能：根据配置项二次处理数据，新增_rule 和 _isShow 属性。
        描述：对传入的配置项进行处理，生成新的对象，添加表单校验规则和显示状态控制属性，返回处理后的对象。
    */
    const computeFormItem = (config, formModel) => {
      const item = { ...config }
      item._rule = getRule(item) // 表单校验
      item._isShow = isFunction(item.isShow) ? !!item.isShow(formModel) : true // 联动: 如果 isShow 是函数则调用并转换为布尔值，否则赋为 true，用于控制表单项的显示状态。
      return item
    }

    /* 
        功能：重新计算 formConfig。
        描述：根据传入的表单配置项和当前的表单模型，通过调用 computeFormItem 函数对每个配置项进行处理，生成新的表单配置项数组。
    */
    const formItems = computed(() => {
      return props.formConfig.map(item => computeFormItem(item, formModel))
    })

    return {
      ...toRefs(state),
      formItems,
      onSubmit,
      OnReset,
      formModel
    }
  },
  /* setup 访问不到 this 就写这里了 */
  mounted() {
    const entries = Object.entries(this.formRef)
    for (const [key, value] of entries) {
      this[key] = value
    }
  }
}
</script>
<style scoped>
.btn_reset {
  margin-right: 8px;
}

.form {
  width: 100%;
  margin: 0 auto;
}
.btns {
  display: flex;
  justify-content: center;
}
.ant-form-item {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  color: rgba(0, 0, 0, 0.85);
  font-size: 14px;
  font-variant: tabular-nums;
  line-height: 1.5715;
  list-style: none;
  font-feature-settings: 'tnum';
  margin-bottom: 24px;
  vertical-align: top;
  justify-content: center;
}
</style>
